package server.classloader;


import java.io.*;

/**
 * 注意:
 * 1. 如果为了省事, 可以直接继承[URLClassLoader], 因为读取字节码文件流已经内部做了
 * 2. 如果还需要解密, 则必须自己实现字节码文件流的获取, 因为解密操作需要自己做
 * 3. 如果需要实现热部署, 使用自定义ClassLoader时则不能调用[loadClass],
 *    由于热部署不能遵循双亲委派；因为它会检测各个类加载器的缓存, 为了绕过缓存检测, 则直接使用[findClass]
 *
 */
public class WebappClassLoader extends ClassLoader{
    private String rootDir;

    public String getRootDir() {
        return rootDir;
    }

    public WebappClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    /**
     * 编写findClass方法的逻辑
     */
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        /**
         * 获取类的class文件字节数组
         * 1. 获取文件名 (全路径,拼接.class)
         * 2. 读取文件流 this.getClass().getResourceAsStream(name);
         * 3. 读取字节
         */

        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            //直接生成class对象
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] getClassDataV1(String className) throws IOException {
        // 1. 获取文件名 (全路径,拼接.class)
        String path = classNameToPath(className);

        // 2. 读取文件流
        /**
         * getResource 和 getResourceAsStream
         * 1. path不以'/'开头时, 默认是从此类所在的包下取资源
         * 2. path以'/'开头时, 则是从项目的ClassPath根下获取资源
         */
        InputStream ins = this.getClass().getResourceAsStream(className);//this.getResourceAsStream()

        // 如果不知输入流的大小, 则使用while循环读取到临时数组, 直到read()返回-1时表明读取完毕
        byte[] bytes = new byte[ins.available()];
        ins.read(bytes);
        ins.close();
        return bytes;
    }

    /**
     * 编写获取class文件并转换为字节码流的逻辑
     */
    private byte[] getClassData(String className) {
        // 读取类文件的字节
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            // 读取类文件的字节码
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 类文件的完全路径
     *
     * @param className
     * @return
     */
    private String classNameToPath(String className) {
        return rootDir + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
    }

    public static void main(String[] args) throws ClassNotFoundException {
        // 指定class文件所在目录
        String rootDir = "/Users/apple/test/learn/src/main/java/";
        //创建自定义文件类加载器
        WebappClassLoader loader = new WebappClassLoader(rootDir);

        try {
            //加载指定的class文件(注意是全限定名)
            Class<?> object1 = loader.loadClass("com.duyu.classloader.User");

            // 绕过loadClass的缓存检测, 实现热加载
            Class<?> object2 = loader.findClass("com.duyu.classloader.User");

            System.out.println(object1.newInstance().toString());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
